﻿namespace EFS.RC.CC.Sessions.OfficeService.Customers
{
	/// <summary>
	/// Customer session
	/// </summary>
	class DialOut : EFS.RC.CC.Sessions.OfficeService.Customers.Customer
	{
		#region Composition

		// Office conversation
		private Microsoft.Rtc.Collaboration.Conversation officeConversation;

		// Customer conversation
		private Microsoft.Rtc.Collaboration.Conversation customerConversation = null;

		// Customer call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall inboundOfficeAudioVideoCall = null;

		// office call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall outboundCustomerAudioVideoCall = null;

		// back to back call into office conversation
		private Microsoft.Rtc.Collaboration.BackToBackCall customerConversationB2BCall;

		// Operator Uri
		private System.String destinationUri = null;

		// Customer Uri
		private System.String sourceUri = null;

		// Customer Name
		private System.String displayName = null;

		// Customer Name
		private System.String displayUri = null;

		// Impersonated uri
		private System.String participantEndPointUri = null;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="customersOfficeServiceEndPointApplication"></param>
		/// <param name="toHeader"></param>
		public DialOut(EFS.RC.CC.Applications.EndPoints.OfficeService.Customers customersOfficeServiceEndPointApplication, Microsoft.Rtc.Signaling.FromToHeader fromHeader, Microsoft.Rtc.Signaling.FromToHeader toHeader)
			: base(customersOfficeServiceEndPointApplication)
		{
			// check
			EFS.Common.Verify.Reference(customersOfficeServiceEndPointApplication, "customersOfficeServiceEndPointApplication");
			EFS.Common.Verify.Reference(toHeader, "toHeader");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "DialOut:ctor", destinationUri, GetType());

			// set locals
			sourceUri = fromHeader.Uri;
			destinationUri = toHeader.Uri;
			
			// make display name
			displayUri = EFS.RC.Common.Utilities.SipUriCleanup(toHeader.Uri);

			// display name
			if (EFS.Common.Verify.String(toHeader.DisplayName))
			{
				displayName = toHeader.DisplayName;
			}
			else
			{
				displayName = displayUri;
			}

			// log
			EFS.EventLog.Log.LogInfo(System.String.Format("Dial out to {0} from {1}", destinationUri, sourceUri), this);
			
			// make impersonated uri
			participantEndPointUri = System.String.Format("sip:{0}@{1}", ConversationGuid, customersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint.DefaultDomain);
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Sessions.OfficeService.Customers.DialOut [{0}] {1}", CustomersOfficeServiceEndPointApplication.Describe(), DisplayUri);
		}

		/// <summary>
		/// Display name
		/// </summary>
		public override System.String DisplayName
		{
			get
			{
				// display name
				if (EFS.Common.Verify.String(displayName))
				{
					return displayName;
				}
				else
				{
					return "";
				}
			}
		}

		/// <summary>
		/// Impersonated uri
		/// </summary>
		public override System.String ParticipantEndPointUri
		{
			get { return participantEndPointUri; }
		}

		/// <summary>
		/// Customer uri
		/// </summary>
		public override System.String CustomerUri
		{
			get { return destinationUri; }
		}

		/// Display uri
		/// </summary>
		public override System.String DisplayUri
		{
			get { return displayUri; }
		}

		/// <summary>
		/// Handle incomming call from conference
		/// </summary>
		/// <param name="call"></param>
		internal EFS.Common.Async.AsyncProcess HandleIncommingCall(Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall audioVideoCall)
		{
			EFS.EventLog.Log.LogInfo("HandleIncommingCall", this);

			// check
			EFS.Common.Verify.Reference(audioVideoCall, "audioVideoCall");

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check not already handled
				if (null != inboundOfficeAudioVideoCall)
				{
					throw new System.InvalidOperationException("Conference call already handled");
				}

				// keep;
				inboundOfficeAudioVideoCall = audioVideoCall;
			}

			// make B2B 
			return BeginAsyncProcess2(MakeB2BCall);
		}

		#endregion

		#region Startup

		/// <summary>
		/// Mark start time
		/// </summary>
		/// <returns></returns>
		protected override System.DateTime MarkStart()
		{
			return EFS.RC.CC.Tools.Loggers.CHL.Add(ConversationGuid, CustomersOfficeServiceEndPointApplication.ApplicationPresence.PresenceEndPointDeployment.ID.Value, DisplayUri, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Customer_StartOut);
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		protected override void SetupSession()
		{
			EFS.EventLog.Log.LogInfo("SetupSession", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "SetupSession", this);

			// call office onference in preparation for b2b call
			BeginAsyncProcess2(JoinOfficeConference);
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void JoinOfficeConference()
		{
			EFS.EventLog.Log.LogInfo("JoinOfficeConference", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "JoinOfficeConference", this); 

			// get conference uri
			System.String conferenceUri = OfficeServiceConference.ConferenceUri;

			// get end point
			Microsoft.Rtc.Collaboration.ApplicationEndpoint applicationEndpoint =CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint;

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// log impersonation
				EFS.EventLog.Log.LogInfo(System.String.Format("Impersonate officeConversation {0}", ParticipantEndPointUri), this);

				// make office conversation
				officeConversation = new Microsoft.Rtc.Collaboration.Conversation(applicationEndpoint);
				officeConversation.Impersonate(participantEndPointUri, null, DisplayUri);
				officeConversation.ApplicationContext = this;
				officeConversation.StateChanged += OnOfficeConversationStateChanged;

				// conference options
				Microsoft.Rtc.Collaboration.ConferenceJoinOptions conferenceJoinOptions = new Microsoft.Rtc.Collaboration.ConferenceJoinOptions();
				conferenceJoinOptions.JoinMode = Microsoft.Rtc.Collaboration.JoinMode.Default;

				// join
				officeConversation.ConferenceSession.BeginJoin(
					conferenceUri,
					conferenceJoinOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndJoinOfficeConference, null));
			}
		}

		/// <summary>
		/// End join
		/// </summary>
		/// <param name="result"></param>
		private void EndJoinOfficeConference(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndJoinOfficeConference", this);

			try
			{
				// end join
				officeConversation.ConferenceSession.EndJoin(ar);

				// call office conference
				BeginAsyncProcess2(CallOfficeConference);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Dial into office conference
		/// </summary>
		private void CallOfficeConference()
		{
			EFS.EventLog.Log.LogInfo("CallOfficeConference", this);
			
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// dial out option
				Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoMcuDialOutOptions options = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoMcuDialOutOptions();
				options.PrivateAssistantDisabled = true;
				options.Media.Add(new Microsoft.Rtc.Collaboration.McuMediaChannel(Microsoft.Rtc.Collaboration.MediaType.Audio, Microsoft.Rtc.Collaboration.McuMediaChannelStatus.SendReceive));
				options.ParticipantUri = ParticipantEndPointUri;

				// dial out
				OfficeConversation.ConferenceSession.AudioVideoMcuSession.BeginDialOut(
					CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint.EndpointUri,
					options,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndDialOut));
			}
		}

		/// <summary>
		/// End establish
		/// </summary>
		/// <param name="result"></param>
		private void EndDialOut(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndDialOut", this);

			try
			{
				// check
				OfficeConversation.ConferenceSession.AudioVideoMcuSession.EndDialOut(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex, OnDialOutException);
			}
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void MakeB2BCall()
		{
			EFS.EventLog.Log.LogInfo("MakeB2BCall", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// EFS.EventLog.Log.LogExtended("MakeB2BCall: Entering lock", this);
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Only Started Custmer Session can MakeB2BCall");
				}

				// Make B2B
				customerConversation = new Microsoft.Rtc.Collaboration.Conversation(inboundOfficeAudioVideoCall.Conversation.Endpoint);
				customerConversation.Impersonate(sourceUri, null, null);
				outboundCustomerAudioVideoCall = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall(customerConversation);

				// attendence
				customerConversation.RemoteParticipantAttendanceChanged += OnRemoteParticipantAttendanceChanged;

				// inbound call settings
				Microsoft.Rtc.Collaboration.BackToBackCallSettings inboundB2BCallSettings = new Microsoft.Rtc.Collaboration.BackToBackCallSettings(inboundOfficeAudioVideoCall);

				// log dialing out
				EFS.EventLog.Log.LogInfo(System.String.Format("Dialing Out to Phone: {0}", destinationUri), this);

				// outbound call settings
				Microsoft.Rtc.Collaboration.BackToBackCallSettings outoundB2BCallSettings = new Microsoft.Rtc.Collaboration.BackToBackCallSettings(outboundCustomerAudioVideoCall, destinationUri);
				Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallEstablishOptions outoundB2BCallOptions = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallEstablishOptions();
				outoundB2BCallSettings.CallEstablishOptions = outoundB2BCallOptions;

				// make call
				customerConversationB2BCall = new Microsoft.Rtc.Collaboration.BackToBackCall(
					outoundB2BCallSettings,
					inboundB2BCallSettings);

				// establish call
				customerConversationB2BCall.BeginEstablish(
						EFS.Common.Async.AsyncResult.StateDelegate.End,
						EFS.Common.Async.AsyncResult.StateDelegate.State(OnEndEstablishB2BCall));
			}
		}

		/// <summary>
		/// End B2B call
		/// </summary>
		/// <param name="ar"></param>
		private void OnEndEstablishB2BCall(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("OnEndEstablishB2BCall", this);

			try
			{
				// end establish call
				customerConversationB2BCall.EndEstablish(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex, OnDialOutException);
			}
		}

		/// <summary>
		/// On Exception
		/// </summary>
		/// <param name="ex"></param>
		protected void OnDialOutException(System.Exception ex)
		{
			// check exception
			if (ex is Microsoft.Rtc.Signaling.OperationFailureException && ex.InnerException is Microsoft.Rtc.Signaling.FailureResponseException)
			{
				// check message
				if (ex.InnerException.Message.Contains("A 486 (Busy here)"))
				{
					// busy here
					OfficeServiceConference.StartOperatorsBusyPlayer();

					// shut down
					BeginShutdown();

					// done
					return;
				}
			}

			// error here
			OfficeServiceConference.StartOperatorsDialErrorPlayer();

			// handle default
			HandleExceptionSafe(ex);
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Outside call
		/// </summary>
		protected override Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall OutsideAudioVideoCall
		{
			get { return outboundCustomerAudioVideoCall; }
		}

		/// <summary>
		/// Inside call
		/// </summary>
		protected override Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall InsideAudioVideoCall
		{
			get { return inboundOfficeAudioVideoCall; }
		}

		/// <summary>
		/// Office conversation
		/// </summary>
		protected override Microsoft.Rtc.Collaboration.Conversation OfficeConversation
		{
			get { return officeConversation; }
		}

		/// the session gets terminated.
		/// </summary>
		private void OnRemoteParticipantAttendanceChanged(object sender, Microsoft.Rtc.Collaboration.ParticipantAttendanceChangedEventArgs e)
		{
			EFS.EventLog.Log.LogInfo("OnRemoteParticipantAttendanceChanged", this);

			try
			{
				// if removed, if is me, shut down
				if (e.Removed.Count > 0)
				{
					// customer has left, close session
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// State changed for office conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnOfficeConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			EFS.EventLog.Log.LogInfo("OnOfficeConversationStateChanged", this);

			try
			{
				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - office conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// State changed for office conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnCustomerConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			EFS.EventLog.Log.LogInfo("OnCustomerConversationStateChanged", this);

			try
			{
				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - office conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// <summary>
		/// Remove customer from office conference
		/// </summary>
		protected override void CleanupSession()
		{
			// log
			EFS.EventLog.Log.LogInfo("CleanupSession", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "CleanupSession", this); 

			// make termination
			EFS.RC.CC.Tools.Functions.Termination termination = new EFS.RC.CC.Tools.Functions.Termination(this);

			// remove delegates
			officeConversation.StateChanged -= OnOfficeConversationStateChanged;
			customerConversation.RemoteParticipantAttendanceChanged -= OnRemoteParticipantAttendanceChanged;
			customerConversation.StateChanged -= OnCustomerConversationStateChanged;

			// terminate all
			termination.Terminate(customerConversationB2BCall, OnEndTerminateB2BCall);
		}

		/// End B2B
		/// </summary>
		/// <param name="ar"></param>
		private void OnEndTerminateB2BCall(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("OnEndTerminateB2BCall", this);

			try
			{
				// end terminate
				customerConversationB2BCall.EndTerminate(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		#endregion
	}
}
